package userimage.texteditor;

import global.Utilities;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

import static global.ProjectData.*;

public class TextCore {

    boolean just_initialized = true;
    boolean text_changed = true;
    boolean linenum_changed = true;
    boolean caret_blinged = true;
    boolean isBigFile = false;
    Queue<Integer> text_update_queue = new LinkedList<>();
    Thread caret_bling;
    TextEditor textEditor;
    File openedf;
    FileReader reader;
    TextController controller = new TextController(this);
    char[] buff = new char[8192];
    int linenumber_width = 20;//除行号以外的那栏的宽度
    int[] line_section = {0, 0};
    ArrayList<int[]> editing = new ArrayList<>();
    ArrayList<int[]> start_choosing = new ArrayList<>();
    boolean choosingbg_changed = false;
    boolean caret_status = false;

    public TextCore(TextEditor textEditor) throws FileNotFoundException {
        this.textEditor = textEditor;
        editing.add(new int[]{1, 0});
        start_choosing.add(new int[]{1, 0});

        caret_bling = new Thread(new CaretBlinging());
        caret_bling.start();

        loadFile();
    }

    public boolean doBackspace(KeyEvent keyEvent) {
        for (int i = 0; i < editing.size(); i++) {
            int forelinel = controller.getLineLength(editing.get(i)[0] - 1);
            controller.deleteChar(editing.get(i));
            editing.set(i, new int[]{editing.get(i)[0], editing.get(i)[1] - 1});
            if (editing.get(i)[1] == -1)
                editing.set(i, new int[]{editing.get(i)[0] - 1, forelinel});
            if (editing.get(i)[0] == 0)
                editing.set(i, new int[]{1, 0});
            start_choosing.set(i, editing.get(i));
        }
        if (keyEvent.isControlDown()) {
            if (editing.get(0)[1] == 0)
                return false;
            if (controller.getChar(editing.get(0)[0], editing.get(0)[1] + 1) == controller.getChar(editing.get(0)[0], editing.get(0)[1]))
                return true;
            return !splitingTokens.contains(new String(controller.getChar(editing.get(0)) + ""));
        } else {
            return false;
        }
    }

    public boolean doDelete(KeyEvent keyEvent) {
        for (int[] ints : editing) {
            if (ints[1] == controller.getLineLength(ints[0])) {
                try {
                    controller.deleteChar(ints[0] + 1, 0);
                } catch (ArrayIndexOutOfBoundsException ignored) {
                }
                continue;
            }
            controller.deleteChar(ints[0], ints[1] + 1);
        }
        if(keyEvent.isControlDown()) {
            return !splitingTokens.contains("" + controller.getChar(editing.get(0)[0], editing.get(0)[1] + 1));
        } else
            return false;
    }

    public boolean doCaretLeft(KeyEvent keyEvent) {
        for (int i = 0; i < editing.size(); i++) {
            if (editing.get(i) != start_choosing.get(i) && !keyEvent.isShiftDown()) {
                editing.set(i, Utilities.vertexBeforer(editing.get(i), start_choosing.get(i)));
                start_choosing.set(i, editing.get(i));
            } else {
                int[] tmp = editing.get(i);
                editing.set(i, caretToCoordination(editing.get(i)[0], editing.get(i)[1] - 1));
                if (!keyEvent.isShiftDown())
                    start_choosing.set(i, editing.get(i));
                if (tmp[0] > 1 && editing.get(i)[1] == tmp[1]) {
                    editing.set(i, new int[]{tmp[0] - 1, controller.getLineLength(tmp[0] - 1)});
                    if (!keyEvent.isShiftDown())
                        start_choosing.set(i, editing.get(i));
                }
            }
        }
        if (keyEvent.isControlDown()) {
            if (editing.get(0)[1] == 0)
                return false;
            if (controller.getChar(editing.get(0)[0], editing.get(0)[1] + 1) == controller.getChar(editing.get(0)[0], editing.get(0)[1]))
                return true;
            if (splitingTokens.contains(new String(controller.getChar(editing.get(0)[0], editing.get(0)[1] + 1) + ""))
                    && !splitingTokens.contains(new String(controller.getChar(editing.get(0)[0], editing.get(0)[1]) + "")))
                return false;
            return !splitingTokens.contains(new String(controller.getChar(editing.get(0)) + ""));
        } else {
            return false;
        }
    }

    public boolean doCaretRight(KeyEvent keyEvent) {
        for (int i = 0; i < editing.size(); i++) {
            if (editing.get(i) != start_choosing.get(i) && !keyEvent.isShiftDown()) {
                editing.set(i, Utilities.vertexAfterer(editing.get(i), start_choosing.get(i)));
                start_choosing.set(i, editing.get(i));
            } else {
                int[] tmp = editing.get(i);
                editing.set(i, caretToCoordination(editing.get(i)[0], editing.get(i)[1] + 1));
                if (!keyEvent.isShiftDown())
                    start_choosing.set(i, editing.get(i));
                if (tmp[0] < controller.getLineNum() && editing.get(i)[1] == tmp[1]) {
                    editing.set(i, new int[]{tmp[0] + 1, 0});
                    if (!keyEvent.isShiftDown())
                        start_choosing.set(i, editing.get(i));
                }
            }
        }
        if (keyEvent.isControlDown()) {
            if (editing.get(0)[1] == 0)
                return false;
            if (controller.getChar(editing.get(0)[0], editing.get(0)[1] + 1) == controller.getChar(editing.get(0)[0], editing.get(0)[1]))
                return true;
            if (splitingTokens.contains(new String(controller.getChar(editing.get(0)[0], editing.get(0)[1] + 1) + ""))
                    && !splitingTokens.contains(new String(controller.getChar(editing.get(0)[0], editing.get(0)[1]) + "")))
                return false;
            return !splitingTokens.contains(new String(controller.getChar(editing.get(0)) + ""));
        } else {
            return false;
        }
    }

    public void doCaretUp(KeyEvent keyEvent) {
        for (int i = 0; i < editing.size(); i++) {
            if (editing.get(i) != start_choosing.get(i) && !keyEvent.isShiftDown()) {
                editing.set(i, Utilities.vertexBeforer(editing.get(i), start_choosing.get(i)));
                start_choosing.set(i, editing.get(i));
            } else {
                editing.set(i, caretToCoordination(editing.get(i)[0] - 1, editing.get(i)[1]));
                if (!keyEvent.isShiftDown())
                    start_choosing.set(i, editing.get(i));
                if (editing.get(i)[0] == 0) {
                    editing.set(i, caretToCoordination(new int[]{1, 0}));
                    if (!keyEvent.isShiftDown())
                        start_choosing.set(i, editing.get(i));
                }
            }
        }
    }

    public void doCaretDown(KeyEvent keyEvent) {
        for (int i = 0; i < editing.size(); i++) {
            if (editing.get(i) != start_choosing.get(i) && !keyEvent.isShiftDown()) {
                editing.set(i, Utilities.vertexAfterer(editing.get(i), start_choosing.get(i)));
                start_choosing.set(i, editing.get(i));
            } else {
                int[] tmp = editing.get(i);
                try {
                    editing.set(i, caretToCoordination(editing.get(i)[0] + 1, editing.get(i)[1]));
                } catch (IndexOutOfBoundsException ignored) {
                }
                if (!keyEvent.isShiftDown())
                    start_choosing.set(i, editing.get(i));
                if (tmp[0] == editing.get(i)[0]) {
                    editing.set(i, caretToCoordination(new int[]{controller.getLineNum(), controller.getLineLength(tmp[0])}));
                    if (keyEvent.isShiftDown())
                        start_choosing.set(i, editing.get(i));
                }
            }
        }
    }

    public void doEnter(KeyEvent keyEvent) {

    }

    public void loadFile() throws FileNotFoundException {
        openedf = new File(projectDir + textEditor.getOpenFile());
        reader = new FileReader(openedf);
        try {
            if (openedf.length() > 8192) {
                isBigFile = true;
                dummy = reader.read(buff, 0, 8192);
                controller.setText(new String(buff, 0, 8192));
            } else {
                dummy = reader.read(buff, 0, (int) openedf.length());
                controller.setText(new String(buff, 0, (int) openedf.length()));
            }
            reader.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void setCaretVisible(boolean b) throws IndexOutOfBoundsException {
        caret_status = b;
        caret_blinged = true;
        text_changed = true;
        linenum_changed = true;
        choosingbg_changed = true;
        for (int i = 0; i < editing.size(); i++) {
            int stline = Math.min(editing.get(i)[0], start_choosing.get(i)[0]);
            int edline = Math.max(editing.get(i)[0], start_choosing.get(i)[0]);
            for (int j = stline; j <= edline; j++) text_update_queue.add(j);
        }
        textEditor.repaint();
    }

    public int getLinenumber_width() {
        return theme.linenum_tab + theme.charwidth * Integer.toString(controller.getLineNum()).length() + linenumber_width;
    }

    public int[] toTextCoordination(int x, int y) {
        int[] coor = new int[2];
        coor[0] = y / (theme.font_height + theme.vertical_space) + 1;
        coor[1] = (x - getLinenumber_width()) / theme.charwidth;
        return coor;
    }

    public int[] caretToCoordination(int[] location) {
        int[] coor = new int[2];
        coor[0] = Math.min(location[0], controller.getLineNum());
        coor[1] = Math.max(Math.min(location[1], controller.getLineLength(coor[0])), 0);
        return coor;
    }

    public int[] caretToCoordination(int x, int y) throws IndexOutOfBoundsException {
        int[] coor = new int[2];
        coor[0] = Math.min(x, controller.getLineNum());
        coor[1] = Math.max(Math.min(y, controller.getLineLength(x)), 0);
        return coor;
    }

    public void paintBackground(Graphics2D g) {
        g.setColor(theme.editorarea);
        g.fillRect(0, 0, textEditor.getWidth(), textEditor.getHeight());
    }

    public void paintLineNum(Graphics2D g) {//TODO 在加入滚动功能后，文字显示不再从坐标0开始，要改paintLineNum函数和paintText函数
        g.setColor(theme.editorarea);
        g.fillRect(0, 0, getLinenumber_width(), textEditor.getHeight());
        for (int i = 1; i <= controller.getLineNum(); i++) {
            g.setColor(theme.linenum);
            for (int[] x : editing)
                if (x[0] == i) {
                    g.setColor(theme.editing_line);
                    g.fillRect(getLinenumber_width(), (theme.font_height + theme.vertical_space) * (i - 1), textEditor.getWidth() - getLinenumber_width(), theme.font_height + theme.vertical_space);
                    g.setColor(theme.linenum_editing);
                }
            g.drawString("0".repeat(Integer.toString(controller.getLineNum()).length() - Integer.toString(i).length()) + i, theme.linenum_tab, 2 + theme.font_height * i + theme.vertical_space * (i - 1));
        }
    }

    public void paintText(Graphics2D g) {
        g.setColor(theme.text_base_color);
        if (just_initialized) {
            for (int i = 1; i <= controller.getLineNum(); i++) {
                g.drawString(controller.getLine(i), getLinenumber_width(), 2 + theme.font_height * i + theme.vertical_space * (i - 1));
            }
            text_update_queue.clear();
        } else {
            while (!text_update_queue.isEmpty()) {
                int l = text_update_queue.element();
                text_update_queue.remove();
                g.setColor(theme.text_base_color);
                g.drawString(controller.getLine(l), getLinenumber_width(), 2 + theme.font_height * l + theme.vertical_space * (l - 1));
            }
        }
    }

    public void paintCaret(Graphics2D g) {
        if (caret_status) {
            g.setColor(theme.caret);
        } else {
            return;
        }
        for (int[] coor : editing) {
            g.fillRect(coor[1] * theme.charwidth + getLinenumber_width(), theme.font_height * coor[0] + theme.vertical_space * (coor[0] - 1) + 4, theme.charwidth, 2);
        }
    }

    public void paintChoosing(Graphics2D g) throws IndexOutOfBoundsException {
        g.setColor(theme.text_chose_bg);
        for (int i = 0; i < editing.size(); i++) {
            if (editing.get(i)[0] < start_choosing.get(i)[0]) {
                paintChoosingLine(g, editing.get(i)[0], editing.get(i)[1]);
                paintChoosingLine(g, start_choosing.get(i)[0], 0, start_choosing.get(i)[1]);
                for (int j = editing.get(i)[0] + 1; j < start_choosing.get(i)[0]; j++) paintChoosingLine(g, j);
                g.setColor(theme.editorarea);
                paintChoosingLine(g, start_choosing.get(i)[0], start_choosing.get(i)[1]);
            } else if (editing.get(i)[0] > start_choosing.get(i)[0]) {
                paintChoosingLine(g, start_choosing.get(i)[0], start_choosing.get(i)[1]);
                paintChoosingLine(g, editing.get(i)[0], 0, editing.get(i)[1]);
                for (int j = start_choosing.get(i)[0] + 1; j < editing.get(i)[0]; j++) paintChoosingLine(g, j);
                g.setColor(theme.editorarea);
                paintChoosingLine(g, start_choosing.get(i)[0], 0, start_choosing.get(i)[1]);
            } else {
                int st = Math.min(editing.get(i)[1], start_choosing.get(i)[1]);
                int ed = Math.max(editing.get(i)[1], start_choosing.get(i)[1]);
                paintChoosingLine(g, editing.get(i)[0], st, ed);
            }
        }
    }

    private void paintChoosingLine(Graphics2D g, int line) {
        int len = controller.getLineLength(line) + 1;
        g.fillRoundRect(getLinenumber_width(), (theme.font_height + theme.vertical_space) * (line - 1), len * theme.charwidth, theme.font_height + theme.vertical_space, 4, 4);
    }

    private void paintChoosingLine(Graphics2D g, int line, int start) {//从start开始直到行尾（包括换行符）
        int len = controller.getLineLength(line) - start + 1;
        g.fillRoundRect(getLinenumber_width() + theme.charwidth * start, (theme.font_height + theme.vertical_space) * (line - 1), len * theme.charwidth, theme.font_height + theme.vertical_space, 4, 4);
    }

    private void paintChoosingLine(Graphics2D g, int line, int start, int end) {
        int len = end - start;
        g.fillRoundRect(getLinenumber_width() + start * theme.charwidth, (theme.font_height + theme.vertical_space) * (line - 1), len * theme.charwidth, theme.font_height + theme.vertical_space, 4, 4);
    }

    public class CaretBlinging implements Runnable {
        @SuppressWarnings("BusyWait")
        @Override
        public synchronized void run() {
            //noinspection InfiniteLoopStatement
            while (true) {
                while (true) {
                    try {
                        setCaretVisible(true);
                        Thread.sleep(600);
                        setCaretVisible(false);
                        Thread.sleep(450);
                    } catch (InterruptedException e) {
                        just_initialized = true;
                        linenum_changed = true;
                        text_changed = true;
                        caret_blinged = true;
                        break;
                    } catch (IndexOutOfBoundsException ignored) {
                    }
                }
            }
        }
    }
}
